﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Json;
using TinyROS.Messages;
using TinyROS.Exceptions;

namespace TinyROS
{
    public class MapClientNode : Node, ICommandInteractive
    {
        private Publisher<MapMessage> MapPublisher;
        private Publisher<ByteStreamMessage> MapStreamPublisher;
        private Subscriber<StringMessage> CommandSubscriber;
        private Dictionary<Publisher, string> pubedTopicName = new();
        private Dictionary<Subscriber, string> subedTopicName = new();
        private HttpClient mapClient;
        public MapClientNode() :
            this(TinyROSConfig.DefaultTopicName.Map,
                TinyROSConfig.DefaultTopicName.MapStream,
                TinyROSConfig.DefaultTopicName.NodeCommand.MapClient)
        {

        }
        public MapClientNode(string mapTopicName, string mapStreamTopicName, string commandTopicName)
        {
            this.MapPublisher = new();
            this.MapStreamPublisher = new();
            this.PublishedTopicName.Add(this.MapPublisher, mapTopicName);
            this.PublishedTopicName.Add(this.MapStreamPublisher, mapStreamTopicName);
            this.CommandSubscriber = new((StringMessage msg) => { this.ExecuteCommandMute(msg.Value); });
            this.SubscribedTopicName.Add(this.CommandSubscriber, commandTopicName);
            this.mapClient = new HttpClient();
        }
        public override Dictionary<Publisher, string> PublishedTopicName { get => this.pubedTopicName; }
        public override Dictionary<Subscriber, string> SubscribedTopicName { get => this.subedTopicName; }
        private bool running = false;
        public override bool IsRunning => this.running;

        public void ExecuteCommand(string cmd, bool raiseError = true)
        {
            try
            {
                (var type, var args) = this.ParseCommand(cmd);
                SystemCore.ConsoleWriteLineWithColor("MapClient: begin requseting new map", ConsoleColor.White);
                //this.CreateAndFetchMapAsync((int)args[0], (int)args[1], (int)args[2]);
                this.CreateMap((int)args[0], (int)args[1], (int)args[2]);
                this.RequestAndPublishMap();
                SystemCore.ConsoleWriteLineWithColor("MapClient: 成功刷新地图", ConsoleColor.Green);

            }
            catch
            {
                if (raiseError) { throw; }
            }
        }

        public void ExecuteCommandMute(string cmd, bool raiseError = true)
        {
            try
            {
                (var type, var args) = this.ParseCommand(cmd);
                this.CreateAndFetchMapAsync((int)args[0], (int)args[1], (int)args[2]);
            }
            catch
            {
                if (raiseError) { throw; }
            }
        }

        private enum CommandType { RefreshMap }
        private (CommandType type, object[] args) ParseCommand(string cmd)
        {
            try
            {
                var s = cmd.Split(" ");
                if (s[0] != "refreshmap")
                {
                    throw new CommandNotFoundException();
                }
                if(s.Length != 1 && s.Length !=4)
                {
                    throw new ArgumentCountException();
                }
                if(s.Length == 1)
                {
                    return (CommandType.RefreshMap, new object[] { 100, 10, 50 });
                }
                try
                {
                    var size = int.Parse(s[1]);
                    var obs_num = int.Parse(s[2]);
                    var obs_per = int.Parse(s[3]);
                    return (CommandType.RefreshMap, new object[] { size, obs_num, obs_per});
                }
                catch
                {
                    throw new InvalidArgumentException();
                }
            }
            catch(CommandNotFoundException)
            {
                throw;
            }
            catch(ArgumentCountException)
            {
                throw;
            }
            catch(InvalidArgumentException)
            {
                throw;
            }
            catch
            {
                throw;
            }
        }

        public override void Init(SystemCore master)
        {
            var mapTopic = master.RequestTopic<MapTopic>(this.PublishedTopicName[this.MapPublisher]);
            this.MapPublisher.SetTopic(mapTopic);
            var mapStreamTopic = master.RequestTopic<ByteStreamTopic>(this.PublishedTopicName[this.MapStreamPublisher]);
            this.MapStreamPublisher.SetTopic(mapStreamTopic);
            var cmdTopic = master.RequestTopic<CommandTopic>(this.SubscribedTopicName[this.CommandSubscriber]);
            this.CommandSubscriber.SetTopic(cmdTopic);
            this.mapClient.BaseAddress = new Uri(TinyROSConfig.NetWork.Protocol.Http + TinyROSConfig.NetWork.MapServer.BaseAddress);
            this.Initialized = true;
        }

        public override void Run()
        {
            if (this.Initialized)
            {
                // this.CreateAndFetchMap();
                this.CommandSubscriber.Start(Subscriber<StringMessage>.SubscribeType.Passive);
                this.running = true;
            }
            else
            {
                throw new NodeNotInitedException();
            }

        }

        public override void Stop()
        {
            this.CommandSubscriber.Stop();
            this.running = false;
        }
        private async void CreateAndFetchMapAsync()
        {
            bool createSucc, getMapSucc;
            do
            {
                createSucc = await this.CreateMap();
            } while (!createSucc);
            do
            {
                getMapSucc = await this.RequestAndPublishMap();
            } while (!getMapSucc);
        }
        private async void CreateAndFetchMapAsync(int size, int obs_nums, int obs_percent)
        {
            bool createSucc, getMapSucc;
            do
            {
                createSucc = await this.CreateMap(size, obs_nums, obs_percent);
            } while (!createSucc);
            do
            {
                getMapSucc = await this.RequestAndPublishMap();
            } while (!getMapSucc);
        }
        private async Task<bool> CreateMap()
        {
            return await this.CreateMap
                (TinyROSConfig.RuntimeParam.Map.Size,
                TinyROSConfig.RuntimeParam.Map.ObstacleNum,
                TinyROSConfig.RuntimeParam.Map.ObstaclePercent);
        }
        private async Task<bool> CreateMap(int size, int obs_nums, int obs_percent)
        {
            var mapParam = new MapParam(size, obs_nums, obs_percent);
            MsgResponse responseMsg;
            using (var createResopse = await this.mapClient.PostAsJsonAsync<MapParam>(TinyROSConfig.NetWork.MapServer.CreateMapURL, mapParam))
            {
                responseMsg = await createResopse.Content.ReadFromJsonAsync<MsgResponse>();
                //Console.WriteLine(responseMsg.succ);
                //Console.WriteLine(responseMsg.Error);
            }
            if (responseMsg == null)
            {
                return false;
            }
            else
            {
                return responseMsg.succ;
            }
        }
        private async Task<bool> RequestAndPublishMap()
        {
            // Console.WriteLine("starting request");
            using (var mapResponse = await this.mapClient.GetAsync(TinyROSConfig.NetWork.MapServer.GetMapURL))
            {
                var mapByteArray = await mapResponse.Content.ReadAsByteArrayAsync();
                File.WriteAllBytes(@"test.png", mapByteArray);
                // Console.WriteLine(mapByteArray.Length);
                using (var mapStream = await mapResponse.Content.ReadAsStreamAsync())
                {
                    using (Bitmap map = new Bitmap(mapStream))
                    {
                        try
                        {
                            var h = map.Height;
                            var w = map.Width;
                            byte[,] mapMatrix = new byte[h, w];
                            for (var x = 0; x < h; x++)
                            {
                                for (var y = 0; y < w; y++)
                                {
                                    // 假定三个通道的值相同
                                    var pixel = map.GetPixel(y, x).R;
                                    mapMatrix[x, y] = (byte)(pixel == 255 ? 0 : 1);
                                    // Console.Write(mapMatrix[x, y]);
                                    // Console.Write(" ");
                                }
                                // Console.WriteLine("");
                            }
                            Atlas atlas = new Atlas(mapMatrix);
                            MapMessage msgMap = new MapMessage(atlas, this);
                            ByteStreamMessage msgMapStream = new ByteStreamMessage(mapByteArray, this);
                            this.MapPublisher.Talk(msgMap);
                            this.MapStreamPublisher.Talk(msgMapStream);
                            return true;
                        }
                        catch
                        {

                        }
                    }
                }
            }
            // Console.WriteLine("success");
            return false;
        }

        public class MapParam
        {
            public int map_size { get; set; }
            public int obstacle_nums { get; set; }
            public int obstacle_persent { get; set; }
            public MapParam()
            {

            }
            public MapParam(int size, int o_num, int o_per)
            {
                this.map_size = size;
                this.obstacle_nums = o_num;
                this.obstacle_persent = o_per;
            }
        }
        public class MsgResponse
        {
            public bool succ { get; set; }
            public string Error { get; set; }
            public MsgResponse()
            {

            }
        }
    }
}
